/*
 * Copyright (c) 2020-2099 the original author or authors. All rights reserve.
 */
package org.tianyun.cloud.context;

import com.fasterxml.jackson.annotation.JsonIgnore;
import org.springframework.beans.BeanUtils;
import org.tianyun.cloud.context.ServletContext.ContextOperator;
import org.tianyun.cloud.context.ServletContext.ServletContextHolder;
import org.tianyun.cloud.exception.NestedException;
import org.tianyun.cloud.utils.ClassUtils;
import org.tianyun.cloud.utils.CryptHelper;
import org.tianyun.cloud.utils.StreamUtils;

import java.util.Map;
import java.util.Map.Entry;
import java.util.function.BiConsumer;

/**
 * Request上下文信息
 *
 * @auther ebert_chan
 */
public class RequestContext implements java.io.Serializable {

    /**
     * serialVersionUID
     */
    private static final long serialVersionUID = 1L;

    /**
     * 空构造方法
     */
    public RequestContext() {
    }

    // getter and setter

    @JsonIgnore
    public String getTraceId() {
        return ServletContextHolder.getValue(ServletContext.CONTEXT_TRACE_ID);
    }

    /**
     * request上下文操作工具类
     *
     * @auther ebert_chan
     */
    public static class RequestContextHolder {

        /**
         * request上下文信息在服务间传递的key名称
         */
        public static final String HEADER_X_REQUEST_TOKEN = "x-request-token";

        /**
         * threadLocal
         */
        private static final ThreadLocal<RequestContext> threadLocal = new ThreadLocal<>();

        /**
         * 空构造方法
         */
        private RequestContextHolder() {
        }

        /**
         * 获取当前线程Request上下文
         *
         * @return
         * @auther ebert_chan
         */
        public static RequestContext get() {
            return get(RequestContext.class);
        }

        /**
         * 获取当前线程Request上下文
         *
         * @param contextClass
         * @return
         * @auther ebert_chan
         */
        @SuppressWarnings("unchecked")
        public static <T extends RequestContext> T get(Class<T> contextClass) {
            RequestContext rc = threadLocal.get();
            T o = null;

            if (rc == null) {
                return o;
            }

            if (rc.getClass().equals(contextClass) || contextClass.equals(ServletContext.class)) {
                return (T) rc;
            }

            o = newInstanceOfType(contextClass);
            BeanUtils.copyProperties(rc, o);
            set(o);

            return o;
        }

        /**
         * 反射获取类的实例
         *
         * @param contextClass
         * @return
         * @auther ebert_chan
         */
        private static <T extends RequestContext> T newInstanceOfType(Class<T> contextClass) {
            T o = null;
            try {
                o = ClassUtils.newInstance(contextClass);
            } catch (Exception e) {
                throw new NestedException("Class '" + contextClass.getName() + "' instantiation failure.", e);
            }
            return o;
        }

        /**
         * 设置上下文信息
         *
         * @return
         * @auther ebert_chan
         */
        public static void set(RequestContext context) {
            threadLocal.set(context);
        }

        /**
         * 设置上下文信息
         *
         * @param contextClass
         * @param setter
         * @param value
         * @throws Exception
         * @auther ebert_chan
         */
        @SuppressWarnings("unchecked")
        public static <T extends RequestContext, V> void set(Class<T> contextClass, BiConsumer<T, V> setter, V value) {
            RequestContext context = get(contextClass);
            T o;
            if (context != null) {
                if (context.getClass().equals(contextClass)) {
                    o = (T) context;
                } else {
                    o = newInstanceOfType(contextClass);
                    BeanUtils.copyProperties(context, o);
                    set(o);
                }
            } else {
                o = newInstanceOfType(contextClass);
                set(o);
            }
            setter.accept(o, value);
        }

        /**
         * 清理当前上下文中的信息
         *
         * @auther ebert_chan
         */
        public static void clear() {
            threadLocal.remove();
        }

        /**
         * 传递Context
         *
         * @param operator
         * @auther ebert_chan
         */
        public static void dispatch(ContextOperator operator) {
            RequestContext context = get();
            if (context != null) {
                byte[] data = StreamUtils.object2Byte(context);
                String value = CryptHelper.base64Encrypt(data);
                if (value != null) {
                    operator.apply(HEADER_X_REQUEST_TOKEN, value);
                }
            }
        }

        /**
         * 设置request上下文信息，从x-request-token字符串中解析
         *
         * @param value
         * @auther ebert_chan
         */
        public static void setFromRequestToken(String value) {
            byte[] data = CryptHelper.base64DecryptToBuffer(value.toString());
            RequestContext context = StreamUtils.byte2Object(data);
            if (context != null) {
                set(context);
            }
        }

        /**
         * 设置上下文信息
         *
         * @param contextMap
         * @auther ebert_chan
         */
        public static void setContextMap(Map<String, Object> contextMap) {
            if (contextMap == null || contextMap.isEmpty()) {
                return;
            }
            for (Entry<String, Object> entry : contextMap.entrySet()) {
                String key = entry.getKey();
                Object value = entry.getValue();
                if (value == null) {
                    continue;
                }
                if (key.equals(HEADER_X_REQUEST_TOKEN)) {
                    setFromRequestToken(value.toString());
                    break;
                }
            }
        }

    }

}
